<?php
class db_adapter {
	/**
	 * db_query instance
	 *
	 * @var db_query
	 */
	private $db_query;

	private static $_instance;

	/**
	 * Returns the only instance of the class
	 *
	 * @return db_adapter
	 */
	public static function &get_instance() {
		if (!isset(self::$_instance)) {
			$c = __CLASS__;
			self::$_instance = new $c;
		}

		return self::$_instance;
	}

	/**
	 * Constructor
	 *
	 * @param db_query $db_query
	 * @return db_adapter
	 */
	private function __construct() {
		$this->db_query = db_factory::get_db();
	}

	/**
	 * Loads table record by criteria
	 *
	 * @param db_table $table_obj
	 * @param query_criteria $criteria if not set loads by primary key value
	 * @return bool
	 */
	public function load(db_table &$table_obj, query_criteria &$criteria = null) {
		$table_name = $table_obj->get_table_name();
		$fields_names = $table_obj->get_fields();

		if ($criteria === null) {
			$criteria = new query_criteria();
			$criteria->add_criterion_field_value($table_obj->get_primary_key(), criterion_type::$equal);
		}

		$sql = "SELECT ".implode(", ", $fields_names)." FROM ".$table_name." WHERE ".$criteria->get_criteria()." LIMIT 0, 1";
		$result = $this->db_query->execute($sql);
		if ($result === false) return false;
		if ($this->db_query->get_num_rows() < 1) return false;
		$row = $this->db_query->fetch_assoc();

		$this->parse_row($row, $table_obj);
		return true;
		
	}
	/**
	 * Loads table record by fields
	 *
	 * @param db_table $table_obj
	 * @return bool
	 */
	public function load_unique(db_table &$table_obj) {		
		$table_name = $table_obj->get_table_name();	
		$criteria = new query_criteria();
		foreach ($table_obj as $field) {
			if ($field->get_value()!== null)
			{			
				$criteria->add_criterion_field_value($field,criterion_type::$equal);				
			}			
		}
		$result = $this->load($table_obj,$criteria);
		return $result;
	}
	

	
	
	/**
	 * Loads last id record by criteria
	 *
	 * @param db_table $table_obj
	 * @param query_criteria $criteria if not set loads by primary key value
	 * @return bool
	 */
	public function get_last_id(db_table &$table_obj, query_criteria &$criteria = null) {
		$table_name = $table_obj->get_table_name();
		$fields_names = array($table_obj->get_primary_key()->get_field_name());
		 
		if ($criteria === null) {
			$criteria = new query_criteria();
			$criteria->add_criterion_field_value($table_obj->get_primary_key(), criterion_type::$equal);
		}

		$sql = "SELECT ".implode(", ", $fields_names)." FROM ".$table_name." ORDER BY " .implode(", ", $fields_names)." DESC LIMIT 0, 1";
		$result = $this->db_query->execute($sql);
		if ($result === false) return false;
		if ($this->db_query->get_num_rows() < 1) return false;
		$row = $this->db_query->fetch_assoc();

		$this->parse_row($row, $table_obj);
		 
		return true;
	}

	/**
	 * Saves (inserts or updates) table record
	 *
	 * @param db_table $table_obj
	 * @return bool
	 */
	public function save(db_table &$table_obj) {
		$table_name = $table_obj->get_table_name();
		$primary = $table_obj->get_primary_key();
		$fields_names = $table_obj->get_fields();

		$is_update = !$table_obj->is_new();

		if ($is_update) {
			$sql = "UPDATE ";
		} else {
			$new_id = $table_obj->custom_id();
			if ($new_id !== false) $primary->set_value($new_id);
			$sql = "INSERT INTO ";
		}

		$sql .= $table_name." SET ";

		$pairs = array();
		foreach ($fields_names as $field_name) {
			$field =& $table_obj->$field_name;
			$value = $field->get_value();
			if ($value === null || ($is_update && ($field_name == $primary->get_field_name() || !$field->is_updated()))) continue;
			$value = (($value !== null) ? $this->db_query->quotes($value) : "NULL");
			$pairs[] = sprintf("%s = %s", $field_name, $value);
		}

		if (count($pairs) < 1) return true;

		$sql .= implode(", ", $pairs);
		if ($is_update) $sql .= sprintf(" WHERE %s = %s", $primary->get_field_name(), $this->db_query->quotes($primary->get_value()));

		$result = $this->db_query->execute($sql);
		if ($result === false) return false;

		if (!$is_update && $new_id === false) {
			$primary->set_value($this->db_query->get_last_insert_id());
		}

		return true;
	}
	
	/**	
	 * Saves unique table record
	 *
	 * @param db_table $table_obj
	 * @return bool
	 */
	public function save_unique(db_table &$table_obj) {
		$result = false;
		if ($this->save($table_obj)) {
			$table_obj->reset_fields();
			$this->get_last_id($table_obj);
			$result = true;			
		} 
		else {			
			$result = $this->load_unique($table_obj);
		}		
		return $result;
	}

	/**
	 * Deletes records from table
	 *
	 * @param db_table $table_obj
	 * @param query_criteria $criteria if not set delete by primary key value
	 */
	public function delete(db_table &$table_obj, query_criteria &$criteria = null) {
		$table_name = $table_obj->get_table_name();

		if ($criteria === null) {
			$criteria = new query_criteria();
			$criteria->add_criterion_field_value($table_obj->get_primary_key(), criterion_type::$equal);
		}
		$sql = sprintf("DELETE FROM %s WHERE %s", $table_name, $criteria->get_criteria());

		return $this->db_query->execute($sql);
	}

	/**
	 * Loads records list by criteria
	 *
	 * @param db_table $table_obj
	 * @param query_criteria $criteria
	 * @param int $total
	 * @param int $start
	 * @param int $limit
	 * @param string $order_by
	 * @param string $order_dir
	 * @return db_list_iterator
	 */
	public function &get_list(db_table &$table_obj, query_criteria &$criteria = null, &$total = null, $start = null, $limit = null, $order_by = null, $order_dir = "asc") {
		$composite_table = new composite_table();
		$table_name = $table_obj->get_table_name();
		$composite_table->$table_name = clone $table_obj;
		if ($criteria === null) $criteria = new query_criteria();
		return $this->get_composite_list($composite_table, $criteria, $total, $start, $limit, $order_by, $order_dir);
	}

	/**
	 * Deletes set of records
	 *
	 * @param array $records
	 * @return bool
	 */
	public function delete_set(array &$records) {
		if (count($records) < 1) return true;

		$ids = array();
		foreach ($records as $db_table_obj) {
			$ids[] = $db_table_obj->get_primary_key()->get_value();
		}
		$criteria = new query_criteria();
		$criteria->add_criterion($records[0]->get_primary_key(), criterion_type::$in, $ids);

		return $this->delete($records[0], $criteria);
	}

	/**
	 * Parses assoc row to table object fields
	 *
	 * @param array $row
	 * @param db_table $record
	 * @param bool $reset_fields
	 */
	public function parse_row(array $row, db_table &$record, $reset_fields = false) {
		if ($reset_fields === true) $record->reset_fields();
		foreach ($row as $key => $value) {
			if (get_magic_quotes_runtime()) $value = stripslashes($value);
			$record->$key->set_value($value, true);
		}
	}

	/**
	 * Loads composite table record by criteria
	 *
	 * @param composite_table $table_obj
	 * @param query_criteria $criteria if not set loads by primary key of leading table value
	 * @return bool
	 */
	public function load_composite(composite_table &$table_obj, query_criteria &$criteria = null) {
		$fields_names = $table_obj->get_fields(true);

		if ($criteria === null) {
			$criteria = new query_criteria();
			$leading_table = $table_obj->get_leading_table();
			$leading_primary = $table_obj->$leading_table->get_primary_key()->get_field_name();
			$prm_value = $table_obj->$leading_table->get_primary_key()->get_value();
			$criteria->add_custom_criterion(sprintf("%s.%s = %s", $leading_table, $leading_primary, $this->db_query->quotes($prm_value)));
		}

		$sql = "SELECT ".implode(", ", $fields_names)." FROM ".$table_obj->get_relations()." WHERE ".$criteria->get_criteria()." LIMIT 0, 1";
		$result = $this->db_query->execute($sql);
		if ($result === false) return false;
		if ($this->db_query->get_num_rows() < 1) return false;
		$row = $this->db_query->fetch_enum();
		if (!$this->parse_composite_row($row, $table_obj)) return false;

		return true;
	}

	/**
	 * Loads records list by criteria
	 *
	 * @param composite_table $table_obj
	 * @param query_criteria $criteria
	 * @param int $total
	 * @param int $start
	 * @param int $limit
	 * @param string $order_by
	 * @param string $order_dir
	 * @return db_list_iterator
	 */
	public function &get_composite_list(composite_table &$table_obj, query_criteria &$criteria = null, &$total = null, $start = null, $limit = null, $order_by = null, $order_dir = "asc") {
		$leading_table_name = $table_obj->get_leading_table();
		$leading_primary_name = $table_obj->$leading_table_name->get_primary_key()->get_field_name();

		if ($order_by === null) $order_by = sprintf("%s.%s", $leading_table_name, $leading_primary_name);
		if ($criteria === null) $criteria = new query_criteria();

		$total = $this->count_composite($table_obj, $criteria);

		$sql = sprintf("SELECT %s FROM %s WHERE %s ORDER BY %s %s", implode(", ", $table_obj->get_fields()), $table_obj->get_relations(), $criteria->get_criteria(), $order_by, $order_dir);
		if ($start !== null) {
			$sql .= sprintf(" LIMIT %s", intval($start));
			if ($limit !== null) {
				$sql .= ", ".$limit;
			}
		}
		$result = $this->db_query->execute($sql);
		if ($result === false) return $result;

		$out = new db_list_iterator($this->db_query->get_result_resource(), $table_obj);
		return $out;
	}

	/**
	 * Parses enum row into composite table object
	 *
	 * @param array $row numeric array row
	 * @param composite_table $table_obj
	 * @param bool $reset_fields
	 * @param resource $res
	 * @return bool
	 */
	public function parse_composite_row($row, composite_table &$table_obj, $reset_fields = false, $res = null) {
		if ($reset_fields) $table_obj->reset_fields();
		foreach ($row as $key => $value) {
			$field_meta = $this->db_query->fetch_field($key, $res);
			if (!$field_meta) return false;
			$field_table = $field_meta->table;
			$field_name = $field_meta->name;
			$table_obj->$field_table->$field_name->set_value($value);
		}
		return true;
	}

	/**
	 * Extended update function
	 *
	 * @param db_table $table_obj table to update
	 * @param query_criteria $criteria update criteria
	 * @param bool $replace replace record on unique duplicate or update $upd_field
	 * @param string $upd_field field name to update when $replace is false
	 * @return bool
	 */
	public function save_ex(db_table &$table_obj, query_criteria &$criteria = null, $replace = true, $upd_field = null) {
		$upd_field = strval($upd_field);
		if (!$replace && !property_exists($table_obj, $upd_field)) return false;

		$table_name = $table_obj->get_table_name();
		$primary = $table_obj->get_primary_key();
		$fields_names = $table_obj->get_fields();

		if ($criteria === null) {
			$criteria = new query_criteria();
			$criteria->add_criterion_field_value($primary, criterion_type::$equal);
		}

		if ($replace) $sql = "REPLACE INTO ";
		else $sql = "UPDATE ";

		$sql .= $table_name . " SET ";

		$pairs = array();
		foreach ($fields_names as $field_name) {
			$field =& $table_obj->$field_name;
			$value = $field->get_value();
			if ($field_name == $primary->get_field_name() || !$field->is_updated() || $value === null) continue;
			$value = $this->db_query->quotes($value);
			$pairs[] = sprintf("%s = %s", $field_name, $value);
		}

		if (count($pairs) < 1) return true;

		$sql .= implode(", ", $pairs);

		if (!$replace) {
			$sql .= "WHERE " . $criteria->get_criteria();
			$sql .= sprintf(" ON DUPLICATE KEY UPDATE %s = %s", $upd_field, $this->db_query->quotes($table_obj->$upd_field->get_value()));
		}

		return $this->db_query->execute($sql);
	}
	
	/**
	 * Gets rows count by critera
	 *
	 * @param db_table $table_obj
	 * @param query_criteria $criteria
	 * @return int
	 */
	public function count(db_table &$table_obj, query_criteria &$criteria) {
		$count_sql = sprintf("SELECT COUNT(%s) AS total FROM %s WHERE %s", $table_obj->get_primary_key()->get_field_name(), $table_obj->get_table_name(), $criteria->get_criteria());
		$total = intval($this->db_query->execute_one($count_sql));
		return $total;
	}
	
	/**
	 * Gets rows count by criteria
	 *
	 * @param composite_table $table_obj
	 * @param query_criteria $criteria
	 * @return int
	 */
	public function count_composite(composite_table &$table_obj, query_criteria &$criteria) {
		$leading_table_name = $table_obj->get_leading_table();
		$leading_primary_name = $table_obj->$leading_table_name->get_primary_key()->get_field_name();
		
		$count_sql = sprintf("SELECT COUNT(%s.%s) AS total FROM %s WHERE %s", $leading_table_name, $leading_primary_name, $table_obj->get_relations(), $criteria->get_criteria());
		$total = intval($this->db_query->execute_one($count_sql));
		return $total;
	}
	
	/**
	 * Inserts set of records to DB
	 *
	 * @param array $list array of db_table objects
	 * @return bool
	 */
	public function save_list(array &$list) {
		$count = count($list);
		if ($count) {
			$table_obj = $list[0];
			$fields_names = $table_obj->get_fields();
			
			$values = array();
			for ($i = 0; $i < $count; $i++) {
				$current_tbl = $list[$i];
				$fields_values = array();
				foreach ($fields_names as $field_name) {
					$fields_values[] = $this->db_query->quotes($current_tbl->$field_name->get_value());
				}
				$values[] = "(" . implode(", ", $fields_values) . ")";
			}
			
			$sql = "INSERT INTO %s (%s) VALUES %s";
			$sql = sprintf($sql, $table_obj->get_table_name(), implode(", ", $fields_names), implode(", ", $values));
			
			return $this->db_query->execute($sql);
		}
		
		return true;
	}
}
?>